﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Gin.Commands;
using Gin.Transactions;
using System.Xml.Serialization;
using System.Security.Principal;

namespace Gin
{

    delegate ExecutionContext ExecuteCommandDelegate(Command command);

    public class Package
    {

        public const string TRANSACTIONS_SUBFOLDER_NAME = @"transactions";
        public const string PACKAGE_DATA_FILENAME = @"data.xml";
        public const string TRANSACTIONS_DATA_FILENAME = @"data.xml";
        public const string MESSAGE_PACKAGE_ALREADY_EXECUTED = "Пакет был выполнен ранее";
        public const string MESSAGE_UNEXISTENT_TRANSACTION = "Транзакция не существует";

        private PackageBody _body;
        private ExecutionContext _context;
        private PackageContent _content;
        private PackageData _packageData;
        private List<Transaction> _transactions;
        private bool _alreadyExecuted = false;

        public string PackagePath { get; private set; }
        public string PackageDataFile { get; private set; }
        public string TransactionsPath { get; private set; }
        public string ContentPath { get; private set; }

        public Package(string filePath, ExecutionContext context)
        {
            _context = context;
            _context.ExecutedPackage = this;
            string contentSubfolderName = Guid.NewGuid().ToString("N");
            ContentPath = Path.Combine(_context.TempPath, contentSubfolderName);
            _content = new PackageContent(ContentPath, filePath);
            string xmlFilePath = _content.GetContent(PackageBuilder.MAIN_PACKAGE_FILENAME);
            _body = GinSerializer.Deserialize<PackageBody>(xmlFilePath);
            InitPackagePaths();
            LoadPackageData();
            CheckAlreadyExecuted();
            LoadTransactions();
        }

        private void InitPackagePaths()
        {
            PackagePath = Path.Combine(_context.PackagesPath, _body.PackageId);
            PackageDataFile = Path.Combine(PackagePath, PACKAGE_DATA_FILENAME);
            TransactionsPath = Path.Combine(PackagePath, TRANSACTIONS_SUBFOLDER_NAME);
        }

        private void LoadPackageData()
        {
            if (String.IsNullOrEmpty(PackageDataFile) || !File.Exists(PackageDataFile))
            {
                return;
            }
            _packageData = GinSerializer.Deserialize<PackageData>(PackageDataFile);
            _alreadyExecuted = true;
        }

        private void CheckAlreadyExecuted()
        {
            _alreadyExecuted = (_packageData != null);
        }

        private void LoadTransactions()
        {
            _transactions = new List<Transaction>();
            if (String.IsNullOrEmpty(TransactionsPath) || !Directory.Exists(TransactionsPath))
            {
                return;
            }
            string[] transactionNames = Directory.GetDirectories(TransactionsPath);
            foreach (string name in transactionNames)
            {
                string transactionPath = Path.Combine(TransactionsPath, name);
                string dataFilePath = Path.Combine(transactionPath, TRANSACTIONS_DATA_FILENAME);
                Transaction transaction = GinSerializer.Deserialize<Transaction>(dataFilePath);
                _transactions.Add(transaction);
            }
        }


        public ExecutionContext Execute()
        {
            if (_alreadyExecuted)
            {
                throw new Exception(MESSAGE_PACKAGE_ALREADY_EXECUTED);
            };

            Directory.CreateDirectory(PackagePath);
            Directory.CreateDirectory(TransactionsPath);
            PackageData data = new PackageData()
            {
                InstallationDate = DateTime.Now,
                InstallationUserName = WindowsIdentity.GetCurrent().User.Value
            };
            GinSerializer.Serialize(data, PackageDataFile);
            ExecuteCommandDelegate delegateExecuteCommand = new ExecuteCommandDelegate(ExecuteRootCommand);
            IAsyncResult ar = delegateExecuteCommand.BeginInvoke(_body.Command,null, _context);
            return _context;
        }

        private ExecutionContext ExecuteRootCommand(Command command)
        {
            command.Do(_context);
            return _context;
        }

        public void Rollback(string transactionName)
        {
            Transaction transaction = GetTransactionByName(transactionName);
            transaction.Rollback();
        }

        public void Rollback()
        {
            foreach (Transaction transaction in _transactions)
            {
                transaction.Rollback();
            }
        }

        private Transaction GetTransactionByName(string transactionName)
        {
            Transaction transaction = _transactions.Where(t => t.TransactionName == transactionName).FirstOrDefault();
            if (transaction == null)
            {
                throw new ArgumentException(MESSAGE_UNEXISTENT_TRANSACTION);
            }
            return transaction;
        }

        public void AddTransaction(Transaction transaction)
        {
            _transactions.Add(transaction);
        }

        public string GetContentFile(string path)
        {
            return path.Replace(PackageBuilder.PACKAGE_CONTENT_TEMPLATE_PATH, ContentPath);
        }

    }
}
